<template>
	<view class="vi_collapse" :class="mainClassStr">
		<view class="vi_collapse_head" @click="onClick()"
			:class="headClassStr"
			:style="headStyle">
			<view class="vi_collapse_title">
				<slot name="title">
					<text v-if="title">{{ title }}</text>
				</slot>
			</view>
			<view v-if="setting.showArrow" class="vi_collapse_arrow" :style="{'transition': `transform ${setting.transition/1000}s`}">
				<text class="iconfont" :class="iconSetting.arrow"></text>
			</view>
		</view>
		<view class="vi_collapse_body" :class="bodyClassStr" :style="bodyStyle">
			<view :id="contentId">
				<slot></slot>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		name:"viCollapse",
		props:{
			//是否默认开启
			opened:{
				type:[Boolean,null],
				default:false
			},
			//标题，或slot代替
			title:{
				type:[String,null],
				default:""
			},
			//显示箭头
			showArrow:{
				type:[Boolean,null],
				default:null
			},
			//标题对齐方式，left、right、center、between
			titleAlign:{
				type:[String,null],
				default:""
			},
			//组件id
			id:{
				type:[String,null],
				default:""
			},
			//组id
			groupId:{
				type:[String,null],
				default:""
			},
			//开启动画
			animation:{
				type:[Boolean, null],
				default:null
			},
			//动画时间，内容区打开和关闭
			transition:{
				type:[Number,null],
				default:0
			},
			mainClass:{
				type:[String,null],
				default:""
			},
			headClass:{
				type:[String,null],
				default:""
			},
			bodyClass:{
				type:[String,null],
				default:""
			},
			mainOpenClass:{
				type:[String,null],
				default:""
			},
			headOpenClass:{
				type:[String,null],
				default:""
			},
			bodyOpenClass:{
				type:[String,null],
				default:""
			},
			//标题栏高，默认3em
			headHeight:{
				type:[String,null],
				default:""
			},
			icon:{
				type:[Object,null],
				default:()=>({})
			},
		},
		data() {
			return {
				isGroup:false,		//是否存在折叠组
				isOpen:false,
				height:"auto",
				contentId:"",		//内容id
				level:0,			//嵌套层级
				dicIcon:{
					arrow:"icon-arr-down"
				}
			}
		},
		computed:{
			//将默认值内含到css、计算属性等地方，只内含真值
			//规避特殊使用的情况下，由prop传入undefined、null等无效数据导致的，默认值失效问题。例如组件嵌套且需要组件间参数传递
			//HBuilder X3.3.13; 微信开发者工具stable 1.05.2111300
			//微信小程序会对传入的undefined、null值根据参数类型输出不同空值(false、""、null、0)，参数类型为null或未指定参数类型则输出null
			//H5会对传入的undefined取默认值，未设默认值则输出undefined。对传入的null值固定输出null
			//做参数传递时需警惕两者行为不同而造成的陷阱，主要问题出现在微信小程序的空值转化，输出非用户传入的空值、假值，使默认真值丢失
			//兼容空值取默认值的可行方案目前为，参数类型加入null如[Boolean,null]，判断传入参数是否为定义的类型，是则使用，否则赋予默认值
			setting(){
				return {
					showArrow: this.$api.isBoolean(this.showArrow)? this.showArrow :true,
					animation: this.$api.isBoolean(this.animation)? this.animation :true,
					transition: this.transition ||300,
					titleAlign: this.titleAlign ||'between',
				}
			},
			headStyle(){
				return this.headHeight? `height:${this.headHeight}em;` :""
			},
			bodyStyle(){
				let res = this.setting.animation ? `transition:height ${this.setting.transition/1000}s;` :"";
				return this.isOpen ? `${res}height:${this.height}` : `${res}height:0;`
			},
			mainClassStr(){
				let res = "";
				res += this.mainClass? this.mainClass+" " :"";
				res += this.isOpen? `${this.mainOpenClass||""} ` :"";
				return res
			},
			headClassStr(){
				let res = "";
				res += this.headClass? this.headClass+" " :"";
				res += this.isOpen? `${this.headOpenClass||""} vi_collapse_act ` :"";
				res += `vi_collapse_${this.setting.titleAlign}`
				return res
			},
			bodyClassStr(){
				let res = "";
				res += this.bodyClass? this.bodyClass+" " :"";
				res += this.isOpen? `${this.bodyOpenClass||""} ` :"";
				return res
			},
			iconSetting(){
				if(this.$api.isObject(this.icon)){
					return Object.assign({},this.dicIcon,this.icon)
				}
				return this.dicIcon
			}
		},
		methods:{
			//打开，组调用，如果当前为关闭状态则执行一次点击事件
			open(){
				if(!this.isOpen){
					this.onClick()
				}
			},
			//关闭，组调用，如果当前为开启状态则执行一次点击事件
			close(){
				if(this.isOpen){
					this.onClick()
				}
			},
			onClick(){
				if (this.disabled){
					return
				}
				//如果高度为默认auto，重新获取一次高度
				if(this.height=="auto"){
					this.getHeight()
				}
				this.isOpen = !this.isOpen;
				//如果存在嵌套，通知上层更新高度
				if(this.level >0){
					let parent = this.getParent();
					//有动画时需延迟
					parent.refresh(this.setting.animation? this.setting.transition :0);
				}
				//如果存在上层组，且当前组件为展开事件，发送事件到组
				if(this.isGroup && this.isOpen){
					let group = this.getGroup();
					if(group){
						group.onChildClick(this.contentId, this.isOpen)
					}
				}
				this.$emit("change", this.isOpen);
			},
			//刷新，下层调用，并已完成动画
			refresh(time){
				//将当前层高度置为自动，目标层的全部上层过渡动画交由渲染引擎处理
				this.height = "auto";
				//在下层动画时间完成后重新获取内层高度，影响过渡动画的再次计算
				setTimeout(()=>{
					this.getHeight();
				}, time)
				//存在上层，通知上层更新高度
				if(this.level >0){
					let parent = this.getParent();
					parent.refresh(time)
				}
			},
			//获取内容高度
			getHeight(){
				const views = uni.createSelectorQuery().in(this);
				views.select(`#${this.contentId}`)
					.fields({
						size: true
					}, (data) => {
						//如果内容没有高度则不处理，使用默认auto
						if(data.height){
							this.height = data.height +"px";
						}
					})
					.exec()
			},
			//获取嵌套（根据名字）
			getParent(){
				return this.$api.getParent(this, "viCollapse")
			},
			//获取组（根据名字）
			getGroup(name="viCollapse"){
				return this.$api.getParent(this, "viCollapseGroup", "groupId")
			}
		},
		watch:{
			"opened":{
				handler(newVal) {
					if(!!newVal != this.isOpen){
						this.onClick()
					}
				}
			}
		},
		created() {
			this.contentId = this.id || `el${Math.ceil(Math.random() * 10e10).toString(36)}`;
			this.isOpen = !!this.opened;
			//检查嵌套
			let parent = this.getParent();
			if(parent){
				this.level = parent.level +1
			}
			//检查组
			if(this.groupId && this.getGroup()){
				this.isGroup = true
			}
		},
		mounted(){
			//渲染后才能拿到高度值
			//微信小程序会在父页面渲染时执行而不是父组件渲染时，如果嵌套了popup父组件，将会因为父组件没有打开而得到高度0
			//已在开关切换状态事件中加入高度重新获取，但不能解决初始化时无高度的问题，将会导致第一次切换时无动画
			//如要彻底解决，应在父组件open完成时手动触发高度获取
			this.getHeight();
		}
	}
</script>

<style lang="scss">
.vi_collapse{
	box-sizing: border-box;
	transition: 0.3s;
	.vi_collapse_head{
		display: flex;
		justify-content: space-between;
		align-items: center;
		height: 3em;
		padding: 0 0.5em;
		box-sizing: border-box;
		transition: 0.3s;
		&.vi_collapse_left{
			justify-content: flex-start;
		}
		&.vi_collapse_center{
			justify-content: center;
		}
		&.vi_collapse_right{
			justify-content: flex-end;
		}
		.vi_collapse_title{
			flex-grow: 1;
		}
		.vi_collapse_arrow{
			flex-shrink: 0;
			margin-left: 0.5em;
			color: #999;
		}
		&.vi_collapse_act{
			.vi_collapse_arrow{
				transform: rotate(-180deg);
			}
		}
	}
	.vi_collapse_body{
		overflow: hidden;
		box-sizing: border-box;
		transition: 0.3s;
	}
}
</style>
